﻿using System.Net;
using System.Numerics;
using System.Text;

namespace newTest;

public class IPSearch
{
    private long[,] _prefMap;
    private byte[] _data;
    private readonly string _readMode;
    private readonly int _version;
    private FileStream _file;
    private long _numbers;

    private string _datPath = "D:\\databases\\ipv6_city.dat";

    private static readonly Lazy<IPSearch> lazy = new Lazy<IPSearch>(() => new IPSearch());

    public static IPSearch Instance
    {
        get { return lazy.Value; }
    }

    private IPSearch()
    {
        LoadDatV6();
    }


    private void LoadDatV6()
    {
        _file = new FileStream(_datPath, FileMode.Open, FileAccess.Read);
        _data = File.ReadAllBytes(_datPath);
        _file.Close();
        _numbers = UnpackInt4Byte(4);
        _prefMap = new long[65600, 2];

        for (int k = 0; k < _numbers; k++)
        {
            int i = k * 12 + 4 + 4;
            long startIndex = UnpackInt4Byte(i);
            long endIndex = UnpackInt4Byte(i + 4);
            long key = UnpackInt4Byte(i + 8);
            _prefMap[key, 0] = startIndex;
            _prefMap[key, 1] = endIndex;
        }
    }

    public string Find(string ip)
    {
        string[] ips = ip.Split(":");

        var addressBytes = IPAddress.Parse(ip).GetAddressBytes();
        BigInteger val = new BigInteger(addressBytes.Reverse().ToArray());
        long pref = Convert.ToInt64(ips[0], 16);
        long low = _prefMap[pref, 0], high = _prefMap[pref, 1];
        long cur = low == high ? low : BinarySearchV6(low, high, val);
        if (cur == 100000000)
        {
            return "|";
        }

        return cur > -1 ? GetAddrV6(cur) : "||||||||||";
    }

    private long BinarySearchV6(long low, long high, BigInteger k)
    {
        long M = 0;
        while (low <= high)
        {
            int mid = (int)(low + high) / 2;
            int p = (int)_numbers * 12 + 4 + 4 + (mid * 55);
            BigInteger endipNum;
            if (!BigInteger.TryParse(Encoding.UTF8.GetString(_data, p, 50).Replace("*", ""), out endipNum))
            {
                throw new Exception("转换biginteger失败");
            }

            if (endipNum.CompareTo(k) >= 0)
            {
                M = mid;
                if (mid == 0)
                {
                    break;
                }

                high = mid - 1;
            }
            else
                low = mid + 1;
        }

        return M;
    }

    private string GetAddrV6(long cur)
    {
        int p = (int)(_numbers * 12 + 4 + 4 + (cur * 55));
        int offset = (int)UnpackInt4Byte(p + 50);
        int length = UnpackInt1Byte(p + 50 + 4);
        return Encoding.UTF8.GetString(_data, offset, length);
    }


    private long UnpackInt4Byte(int offset)
    {
        return (uint)(_data[offset] | (_data[offset + 1] << 8) | (_data[offset + 2] << 16) | (_data[offset + 3] << 24));
    }

    private int UnpackInt1Byte(int offset)
    {
        return (_data[offset]);
    }
}